#include "include/CompressVideo.h"
#include "napi/native_api.h"

#undef LOG_DOMAIN
#undef LOG_TAG
#define LOG_DOMAIN 0x3200
#define LOG_TAG "videoCompressor"
static std::unordered_map<std::string, ProgressTools> S_AllProgress;
static std::unordered_map<std::string, VideoCompressorInfo*> S_AllBean;
std::mutex G_Mutex;
std::condition_variable G_CV;
std::atomic<bool> G_StopFlag(false);

template <class T> T *CheckParamsAndGetThis(const napi_env env, napi_callback_info info, const char *name = nullptr) 
{
    if (env == nullptr || info == nullptr) {
        return nullptr;
    }
    napi_value object = nullptr;
    napi_value propertyNameValue = nullptr;
    napi_value pointerValue = nullptr;
    napi_get_cb_info(env, info, nullptr, nullptr, &object, nullptr);
    if (object != nullptr && name != nullptr) {
        napi_create_string_utf8(env, name, NAPI_AUTO_LENGTH, &propertyNameValue);
    }
    napi_value &resObject = propertyNameValue ? propertyNameValue : object;
    if (resObject) {
        return napi_unwrap(env, resObject, (void **)(&pointerValue)) == napi_ok ? reinterpret_cast<T *>(pointerValue)
                                                                                : nullptr;
    }
    return nullptr;
}

int32_t CreateMutex(VideoCompressorInfo *bean) 
{
    OH_LOG_ERROR(LOG_APP, "CreateMutex start");
    bean->muxer->get()->width = bean->deMuxer->get()->width;
    bean->muxer->get()->height = bean->deMuxer->get()->height;
    bean->muxer->get()->rotation = bean->deMuxer->get()->rotation;
    bean->muxer->get()->videoMime = bean->deMuxer->get()->videoMime;
    bean->muxer->get()->audioMime = bean->deMuxer->get()->audioMime;
    bean->muxer->get()->audioChannelCount = bean->deMuxer->get()->audioChannelCount;
    bean->muxer->get()->audioBitrate = bean->deMuxer->get()->audioBitrate;
    bean->muxer->get()->audioSampleRate = bean->deMuxer->get()->audioSampleRate;
    bean->muxer->get()->videoFrameRate = bean->deMuxer->get()->frameRate;
    int ret = bean->muxer->get()->CreateMuxer(bean->compressParams->outFd);
    return ret;
}

int32_t CreateVideoEncode(VideoCompressorInfo *bean) 
{
    bean->videoEncoder->get()->RegisterMuxerManager(bean->mutexManager->get());
    bean->videoEncoder->get()->width = bean->deMuxer->get()->width;
    bean->videoEncoder->get()->height = bean->deMuxer->get()->height;
    bean->videoEncoder->get()->bitrate = bean->compressParams->quality;
    bean->videoEncoder->get()->frameRate = bean->compressParams->frameRate;
    bean->videoEncoder->get()->videoMime = bean->deMuxer->get()->videoMime;
    bean->videoEncoder->get()->keyProfile = bean->deMuxer->get()->keyProfile;
    int ret = bean->videoEncoder->get()->CreateVideoEncoder(bean->deMuxer->get()->videoMime);
    if (ret != AV_ERR_OK) {
        OH_LOG_ERROR(LOG_APP, "CreateVideoEncoder error");
        return ret;
    }
    ret = bean->videoEncoder->get()->SetVideoEncoderCallback();
    if (ret != AV_ERR_OK) {
        OH_LOG_ERROR(LOG_APP, "VideoEncoder SetVideoEncoderCallback %{public}d", ret);
        return ret;
    }
    ret = bean->videoEncoder->get()->ConfigureVideoEncoder(bean->compressParams->quality);
    if (ret != AV_ERR_OK) {
        OH_LOG_ERROR(LOG_APP, "VideoEncoder ConfigureVideoEncoder %{public}d", ret);
        return ret;
    }
    ret = bean->videoEncoder->get()->GetSurface();
    if (ret != AV_ERR_OK) {
        OH_LOG_ERROR(LOG_APP, "VideoEncoder GetSurface %{public}d", ret);
        return ret;
    }
    bean->videoEncoder->get()->RegisterMuxer(bean->muxer->get());
    return ret;
}

int32_t CreateAudioEncode(VideoCompressorInfo *bean) 
{
    bean->audioEncoder->get()->RegisterMuxerManager(bean->mutexManager->get());
    bean->audioEncoder->get()->audioChannelCount = bean->deMuxer->get()->audioChannelCount;
    bean->audioEncoder->get()->audioSampleRate = bean->deMuxer->get()->audioSampleRate;
    bean->audioEncoder->get()->audioBitrate = bean->deMuxer->get()->audioBitrate;
    int ret = bean->audioEncoder->get()->CreateAudioEncoder("audio/mp4a-latm");
    if (ret != AV_ERR_OK) {
        OH_LOG_ERROR(LOG_APP, "CreateAudioEncoder error");
        return ret;
    }

    ret = bean->audioEncoder->get()->SetAudioEncoderCallback();
    if (ret != AV_ERR_OK) {
        OH_LOG_ERROR(LOG_APP, "AudioEncoder SetAudioEncoderCallback %{public}d", ret);
        return ret;
    }
    ret = bean->audioEncoder->get()->ConfigureAudioEncoder();
    if (ret != AV_ERR_OK) {
        OH_LOG_ERROR(LOG_APP, "AudioEncoder ConfigureAudioEncoder %{public}d", ret);
        return ret;
    }
    bean->audioEncoder->get()->RegisterMuxer(bean->muxer->get());
    return ret;
}

int32_t CreateDemuxer(VideoCompressorInfo *bean) 
{
    bean->deMuxer->get()->RegisterMuxerManager(bean->mutexManager->get());
    int32_t ret = bean->deMuxer->get()->CreateDemuxer(bean->compressParams->inputFd, bean->compressParams->inputOffset,
                                                      bean->compressParams->inputSize);
    if (ret != AV_ERR_OK) {
        OH_LOG_ERROR(LOG_APP, "CreateDemuxer error %{public}d", ret);
        return ret;
    }

    return ret;
}

void CreateVideoDecode(VideoCompressorInfo *bean) 
{
    bean->videoDecoder->get()->RegisterMuxerManager(bean->mutexManager->get());
    bean->videoDecoder->get()->width = bean->deMuxer->get()->width;
    bean->videoDecoder->get()->height = bean->deMuxer->get()->height;
    bean->videoDecoder->get()->videoMime = bean->deMuxer->get()->videoMime;
    bean->videoDecoder->get()->RegisterDeMuxer(bean->deMuxer->get());
    bean->videoDecoder->get()->defaultFrameRate = bean->compressParams->frameRate;
    bean->videoDecoder->get()->VideoEnc = bean->videoEncoder->get();
}

void CreateAudioDecode(VideoCompressorInfo *bean) 
{
    bean->audioDecoder->get()->RegisterMuxerManager(bean->mutexManager->get());
    bean->audioDecoder->get()->audioBitrate = bean->deMuxer->get()->audioBitrate;
    bean->audioDecoder->get()->aacIsADTS = bean->deMuxer->get()->aacIsADTS;
    bean->audioDecoder->get()->audioSampleFormat = bean->deMuxer->get()->audioSampleFormat;
    bean->audioDecoder->get()->audioSampleRate = bean->deMuxer->get()->audioSampleRate;
    bean->audioDecoder->get()->audioChannelCount = bean->deMuxer->get()->audioChannelCount;
    bean->audioDecoder->get()->RegisterDeMuxer(bean->deMuxer->get());
}

void ReturnCompressInfo(napi_env env, void *data) 
{
    AsyncCallbackInfo *compressParams = (AsyncCallbackInfo *)data;
    if (compressParams->resultCode != 0) {
        if (remove(compressParams->outputPath.data()) == 0) {
            OH_LOG_ERROR(LOG_APP, "delete outputFile");
        }
    }
    napi_value code;
    napi_create_int32(env, compressParams->resultCode, &code);
    napi_value value;
    napi_create_string_utf8(env, compressParams->resultStr.data(), NAPI_AUTO_LENGTH, &value);
    napi_value outputPath;
    OH_LOG_ERROR(LOG_APP, "callback outputPath:%{public}s", compressParams->outputPath.c_str());
    napi_create_string_utf8(env, compressParams->outputPath.data(), NAPI_AUTO_LENGTH, &outputPath);
    napi_value obj;
    napi_create_object(env, &obj);
    napi_set_named_property(env, obj, "code", code);
    napi_set_named_property(env, obj, "message", value);
    napi_set_named_property(env, obj, "outputPath", outputPath);
    napi_resolve_deferred(compressParams->env, compressParams->deferred, obj);
    napi_delete_async_work(env, compressParams->asyncWork);
    delete compressParams;
}

void Release(VideoCompressorInfo *bean, const bool &needClearFlag = false) 
{
    if (needClearFlag && bean) {
        G_Mutex.lock();
        if (S_AllBean.find(bean->compressParams->taskID) != S_AllBean.end()) {
            S_AllBean.erase(bean->compressParams->taskID);
        }
        G_Mutex.unlock();
    }

    bean->videoEncoder->get()->ResetVideoEncoder();
    bean->videoDecoder->get()->Release();
    bean->audioDecoder->get()->Release();
    bean->audioEncoder->get()->Release();
    bean->muxer->get()->StopMuxer();
    bean->deMuxer->get()->Release();
    bean->videoDecoder = nullptr;
    bean->audioDecoder = nullptr;
    bean->videoEncoder = nullptr;
    bean->audioEncoder = nullptr;
    bean->muxer = nullptr;
    bean->deMuxer = nullptr;
    bean = nullptr;
}

void VideoCompressorWaitEos(VideoCompressorInfo *bean, bool hasAudio) 
{
    if (bean->videoDecoder->get() != nullptr) {
        bean->videoDecoder->get()->WaitForEos();
    }

    if (bean->videoEncoder != nullptr) {
        bean->videoEncoder->get()->WaitForEos();
    }

    if (hasAudio && bean->audioEncoder->get() != nullptr) {
        bean->audioEncoder->get()->WaitForEos();
    }

    if (hasAudio && bean->audioDecoder->get() != nullptr) {
        bean->audioDecoder->get()->WaitForEos();
    }

    if (bean->muxer != nullptr) {
        bean->muxer->get()->StopMuxer();
    }

    if (bean->deMuxer->get() != nullptr) {
        bean->deMuxer->get()->DestroyDemuxer();
    }

    Release(bean, true);
}

void SetCallBackResult(VideoCompressorInfo *bean, int32_t code, std::string str) 
{
    bean->compressParams->resultCode = code;
    bean->compressParams->resultStr = str;
}

int32_t checkFileFormat(VideoCompressorInfo *bean) 
{
    if (bean->deMuxer->get()->videoMime != "video/avc" && bean->deMuxer->get()->videoMime != "video/hevc") {
        SetCallBackResult(bean, -1, "not support video mime:" + bean->deMuxer->get()->videoMime);
        Release(bean, true);
        return AV_ERR_UNKNOWN;
    }
    if (bean->deMuxer->get()->audioMime != "audio/mp4a-latm" && bean->deMuxer->get()->audioMime != "") {
        SetCallBackResult(bean, -1, "not support audio mime:" + bean->deMuxer->get()->audioMime);
        Release(bean, true);
        return AV_ERR_UNKNOWN;
    }
    return AV_ERR_OK;
}

bool HasAudio(VideoCompressorInfo *bean) 
{ 
    return bean->deMuxer->get()->audioMime != ""; 
}

void StartCompressProcess(VideoCompressorInfo *bean) 
{
    if (CreateDemuxer(bean) != AV_ERR_OK) {
        SetCallBackResult(bean, -1, "CreateDemuxer error");
        Release(bean, true);
        return;
    }
    if (checkFileFormat(bean) != AV_ERR_OK) {
        return;
    }

    bool hasAudio = HasAudio(bean);
    if (CreateMutex(bean) != AV_ERR_OK) {
        SetCallBackResult(bean, -1, "CreateMuxer error");
        Release(bean, true);
        return;
    }

    if (CreateVideoEncode(bean) != AV_ERR_OK) {
        SetCallBackResult(bean, -1, "CreateVideoEncode error");
        Release(bean, true);
        return;
    }

    if (hasAudio && CreateAudioEncode(bean) != AV_ERR_OK) {
        SetCallBackResult(bean, -1, "CreateAudioEncode error");
        Release(bean, true);
        return;
    }
    
    if (bean->videoEncoder->get()->StartVideoEncoder() != AV_ERR_OK) {
        SetCallBackResult(bean, -1, "videoEncode error");
        Release(bean, true);
        return;
    }
    
    if (hasAudio && bean->audioEncoder->get()->StartAudioEncoder() != AV_ERR_OK) {
        SetCallBackResult(bean, -1, "audioEncode error");
        Release(bean, true);
        return;
    }

    CreateVideoDecode(bean);
    if (bean->videoDecoder->get()->RunVideoDec(bean->deMuxer->get()->videoMime) != AV_ERR_OK) {
        SetCallBackResult(bean, -1, "VideoDecoder decode error");
        Release(bean, true);
        return;
    }

    if (hasAudio) {
        CreateAudioDecode(bean);
        if (bean->audioDecoder->get()->RunAudioDec(bean->deMuxer->get()->audioMime) != AV_ERR_OK) {
            SetCallBackResult(bean, -1, "AudioDecoder decode error");
            Release(bean, true);
            return;
        }
    }

    SetCallBackResult(bean, 0, "videoCompressor success");
    VideoCompressorWaitEos(bean, hasAudio);
}

void NativeCompressVideo(napi_env env, void *data) 
{
    AsyncCallbackInfo *compressParams = (AsyncCallbackInfo *)data;
    auto mutexManager = std::make_unique<MutexManager>();
    auto deMuxer = std::make_unique<DeMuxer>();
    auto muxer = std::make_unique<Muxer>();
    auto videoEncoder = std::make_unique<VideoEnc>();
    auto videoDecoder = std::make_unique<VideoDec>();
    auto audioEncoder = std::make_unique<AudioEnc>();
    auto audioDecoder = std::make_unique<AudioDec>();
    VideoCompressorInfo *bean = new VideoCompressorInfo();
    bean->audioDecoder = &audioDecoder;
    bean->audioEncoder = &audioEncoder;
    bean->videoDecoder = &videoDecoder;
    bean->videoEncoder = &videoEncoder;
    bean->deMuxer = &deMuxer;
    bean->muxer = &muxer;
    bean->compressParams = compressParams;
    bean->mutexManager = &mutexManager;
    G_Mutex.lock();
    if (bean->compressParams->taskID != "") {
        videoDecoder->setProgress(&S_AllProgress[bean->compressParams->taskID]);
        S_AllBean[bean->compressParams->taskID] = bean;
        OH_LOG_ERROR(LOG_APP, "compressTrace|开始构建新任务进度 ID=%{public}s", bean->compressParams->taskID.c_str());
    }
    G_Mutex.unlock();
    StartCompressProcess(bean);
}

class VideoCompressor { // 交互
public:
    static napi_value JsConstructor(napi_env env, napi_callback_info info);

    static napi_value CompressVideo(napi_env env, napi_callback_info info);

    static napi_value ScanProgress(napi_env env, napi_callback_info info);
};


napi_value VideoCompressor::JsConstructor(napi_env env, napi_callback_info info) 
{
    napi_value targetObj = nullptr;
    void *data = nullptr;
    size_t argsNum = 0;
    napi_value args[2] = {nullptr};
    napi_get_cb_info(env, info, &argsNum, args, &targetObj, &data);
    auto classBind = std::make_unique<VideoCompressor>();
    napi_wrap(
        env, nullptr, classBind.get(),
        [](napi_env env, void *data, void *hint) {
            VideoCompressor *bind = (VideoCompressor *)data;
            delete bind;
            bind = nullptr;
        },
        nullptr, nullptr);
    return targetObj;
}

napi_value VideoCompressor::CompressVideo(napi_env env, napi_callback_info info) 
{
    size_t argc = 8;
    napi_value args[8] = {nullptr};
    int32_t outFd = true;
    int32_t inputFd = true;
    int32_t inputOffset = 0;
    int64_t inputSize = 0;
    int32_t quality = 0;
    int32_t frameRate = 0;
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    napi_get_value_int32(env, args[0], &outFd);
    napi_get_value_int32(env, args[1], &inputFd);
    napi_get_value_int32(env, args[2], &inputOffset);
    napi_get_value_int64(env, args[3], &inputSize);
    napi_get_value_int32(env, args[4], &quality);
    size_t charLen = 0;
    int len = 1024;
    char output[1024] = {0};
    napi_get_value_string_utf8(env, args[5], output, len, &charLen);
    napi_get_value_int32(env, args[6], &frameRate);
    size_t taskActualID = 0;
    int taskIDLength = 1024;
    char taskIDContent[1024] = {0};
    napi_get_value_string_utf8(env, args[7], taskIDContent, taskIDLength, &taskActualID);
    napi_deferred deferred;
    napi_value promise;
    napi_create_promise(env, &deferred, &promise);
    AsyncCallbackInfo *compressParams = new AsyncCallbackInfo();
    compressParams->env = env;
    compressParams->asyncWork = nullptr;
    compressParams->deferred = deferred;
    compressParams->outFd = outFd;
    compressParams->inputFd = inputFd;
    compressParams->inputOffset = inputOffset;
    compressParams->inputSize = inputSize;
    compressParams->quality = quality;
    compressParams->outputPath = output;
    compressParams->frameRate = frameRate;
    compressParams->taskID = taskIDContent;
    napi_value resourceName;
    OH_LOG_ERROR(
        LOG_APP,
        "compressTrace|The params: inputSize=%{public}d quality=%{public}d outputPath=%{public}s frameRate=%{public}d",
        inputSize, quality, output, frameRate);
    napi_create_string_latin1(env, "videoContent", NAPI_AUTO_LENGTH, &resourceName);
    napi_create_async_work(
        env, nullptr, resourceName, [](napi_env env, void *data) { NativeCompressVideo(env, data); },
        [](napi_env env, napi_status status, void *data) { ReturnCompressInfo(env, data); }, (void *)compressParams,
        &compressParams->asyncWork);
    G_Mutex.lock();
    if (S_AllProgress.find(taskIDContent) != S_AllProgress.end()) {
        OH_LOG_ERROR(LOG_APP, "compressTrace|There is same existed task:%{public}s", taskIDContent);
    } else {
        OH_LOG_ERROR(LOG_APP, "compressTrace|Save a Task: taskIDContent=%{public}s", taskIDContent);
        S_AllProgress[std::string(taskIDContent)] = {static_cast<int32_t>(compressParams->inputSize), 0, 0};
    }
    G_Mutex.unlock();
    napi_queue_async_work(env, compressParams->asyncWork);
    return promise;
}

napi_value VideoCompressor::ScanProgress(napi_env env, napi_callback_info info) 
{
    size_t argc = 1;
    napi_value args[2] = {nullptr};
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    size_t actualLen = 0;
    int targetFileLength = 1024;
    char target[1024] = {0};
    napi_get_value_string_utf8(env, args[0], target, targetFileLength, &actualLen);
    napi_value result = 0;
    int content = -1;
    G_Mutex.lock();
    if (S_AllProgress.find(target) != S_AllProgress.end()) {
        content = S_AllProgress[target].currentProgress;
        if (content == 100) {
            S_AllProgress.erase(target);
        }
    }
    G_Mutex.unlock();
    napi_create_int32(env, content, &result);
    return result;
}

static napi_value StopProgress(napi_env env, napi_callback_info info) 
{
    napi_value result = 0;
    size_t argc = 1;
    napi_value args[1] = {nullptr};
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    size_t actualLen = 0;
    int targetFileLength = 1024;
    char target[1024] = {0};
    napi_get_value_string_utf8(env, args[0], target, targetFileLength, &actualLen);
    OH_LOG_ERROR(LOG_APP, "compressTrace|Cancel Target=%{public}s", target);
    G_Mutex.lock();
    if (S_AllProgress.find(target) != S_AllProgress.end() && S_AllBean.find(target) != S_AllBean.end()) {
        S_AllProgress.erase(target);
        napi_create_int32(env, 0, &result);
        Release(S_AllBean[target]);
        S_AllBean.erase(target);
        G_Mutex.unlock();
        return result;
    }
    G_Mutex.unlock();
    napi_create_int32(env, -1, &result);
    return result;
}

EXTERN_C_START static napi_value Init(napi_env env, napi_value exports) 
{
    napi_property_descriptor classProp[] = {
        {"compressVideoNative", nullptr, VideoCompressor::CompressVideo, nullptr, nullptr, nullptr, napi_default,
         nullptr},
        {"getCurrentProgress", nullptr, VideoCompressor::ScanProgress, nullptr, nullptr, nullptr, napi_default,
         nullptr},
    };

    napi_property_descriptor staticProperty[] = {
        {"cancelCompress", nullptr, StopProgress, nullptr, nullptr, nullptr, napi_default, nullptr},
    };

    napi_value videoCompressor = nullptr;
    const char *classBindName = "videoCompressor";
    napi_define_class(env, classBindName, strlen(classBindName), VideoCompressor::JsConstructor, nullptr, 2, classProp,
                      &videoCompressor);
    napi_set_named_property(env, exports, "newVideoCompressor", videoCompressor);

    napi_define_properties(env, exports, sizeof(staticProperty) / sizeof(staticProperty[0]), staticProperty);

    return exports;
}
EXTERN_C_END

static napi_module VideoCompressModule = 
{
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = Init,
    .nm_modname = "entry",
    .nm_priv = ((void *)0),
    .reserved = {0},
};

extern "C" __attribute__((constructor)) void RegisterEntryModule(void) { napi_module_register(&VideoCompressModule); }